package com.microtf.framework.services.miniapp;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microtf.framework.dto.miniapp.WxLogin;
import com.microtf.framework.exceptions.BizException;
import com.microtf.framework.jpa.StorageRepository;
import com.microtf.framework.jpa.entity.StorageEntity;
import com.microtf.framework.services.SettingService;
import com.microtf.framework.services.miniapp.listener.EventListener;
import com.microtf.framework.services.miniapp.message.MediaCheckReturn;
import com.microtf.framework.services.miniapp.message.input.*;
import com.microtf.framework.services.miniapp.message.input.event.*;
import com.microtf.framework.services.miniapp.message.output.OutMessage;
import com.microtf.framework.utils.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 微信小程序服务接口
 *
 * @author glzaboy
 */
@Service
@Slf4j
public class WxService {

    private SettingService settingService;

    @Autowired
    public void setSettingService(SettingService settingService) {
        this.settingService = settingService;
    }


    private RedisTemplate<String, HttpUtil.HttpAuthReturn> redisTemplate;


    @Resource
    public void setRedisTemplate(RedisTemplate<String, HttpUtil.HttpAuthReturn> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    private RedisTemplate<String, String> redisTemplate2;


    @Resource
    public void setRedisTemplate2(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate2 = redisTemplate;
    }

    private StorageRepository storageRepository;

    @Autowired
    public void setStorageRepository(StorageRepository storageRepository) {
        this.storageRepository = storageRepository;
    }

    private ObjectMapper objectMapper;
    private final InheritableThreadLocal<EventListener> eventListenerThreadLocal=new InheritableThreadLocal<>();

    @Autowired
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    public final String LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code";

    public WxLogin login(String appId, String code) {
        WxAppInfo setting = settingService.getSetting(appId, WxAppInfo.class);
        if (Objects.isNull(setting.getRead())) {
            throw new BizException("没有读取到配置");
        }
        HttpUtil.HttpRequest.HttpRequestBuilder builder = HttpUtil.HttpRequest.builder();
        builder.url(LOGIN_URL);
        Map<String, String> opt = new HashMap<>(16);
        opt.put("appid", appId);
        opt.put("secret", setting.getAppSecret());
        opt.put("js_code", code);
        builder.query(opt);
        HttpUtil.HttpResponse sent = HttpUtil.sent(builder.build());
        return sent.json(WxLogin.class);
    }

    /**
     * 微信获取程序access Token
     * 入参 httpAuth 的user 传入应用ID 应用需要在setTing中进行配置配置的格式参考WxAppInfo
     */
    public final Function<HttpUtil.HttpAuth, HttpUtil.HttpAuthReturn> httpBearValue = (HttpUtil.HttpAuth httpAuth) -> {
        HttpUtil.HttpAuthReturn httpAuthReturn = redisTemplate.opsForValue().get(httpAuth.getUser() + httpAuth.getPwd());
        if (httpAuthReturn != null) {
            return httpAuthReturn;
        }
        HttpUtil.HttpAuthReturn httpAuthReturn1 = new HttpUtil.HttpAuthReturn();
        HttpUtil.HttpRequest.HttpRequestBuilder builder = HttpUtil.HttpRequest.builder();
        builder.url("https://api.weixin.qq.com/cgi-bin/token");
        WxAppInfo setting = settingService.getSetting(httpAuth.getUser(), WxAppInfo.class);
        Map<String,String> query=new HashMap<>(8);
        query.put("grant_type","client_credential");
        query.put("appid",setting.getAppId());
        query.put("secret",setting.getAppSecret());
        builder.method(HttpUtil.Method.GET).query(query);
        HttpUtil.HttpResponse sent = HttpUtil.sent(builder.build());
        if (sent.getStatus() == HttpURLConnection.HTTP_OK) {
            WxToken json = sent.json(WxToken.class);
            if(json.getAccessToken()!=null){
                httpAuthReturn1.setAuthValue(json.getAccessToken());
                httpAuthReturn1.setRequestParamName("access_token");
                redisTemplate.opsForValue().set(httpAuth.getUser() + httpAuth.getPwd(), httpAuthReturn1, json.getExpiresIn() - 1800, TimeUnit.SECONDS);
                return httpAuthReturn1;
            }
            return null;
        } else {
            return null;
        }
    };

    /**
     * 检测文本安全性
     * @param content 文本内容
     * @param appId 应用ID
     * @param openId openId
     * @return 安全性
     */
    public Optional<String> checkTextSec(String content,String appId,String openId){
        HttpUtil.HttpRequest.HttpRequestBuilder httpRequestBuilder=HttpUtil.HttpRequest.builder();
        httpRequestBuilder.url("https://api.weixin.qq.com/wxa/msg_sec_check");
        httpRequestBuilder.authFunction(httpBearValue);
        httpRequestBuilder.auth(HttpUtil.HttpAuth.builder().user(appId).build());
        httpRequestBuilder.method(HttpUtil.Method.JSON);
        HashMap<String,String> form=new HashMap<>();
        form.put("content",content);
        form.put("version","2");
        form.put("scene","1");
        form.put("openid",openId);
        try {
            log.info("发送到微信服务器json {}",objectMapper.writeValueAsString(form));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        httpRequestBuilder.postObject(form);

        HttpUtil.HttpResponse sent = HttpUtil.sent(httpRequestBuilder.build());
        MediaCheckEvent json = sent.json(MediaCheckEvent.class);
        if(Objects.nonNull(json.getErrcode()) && json.getErrcode()==0){
            return Optional.of(json.getResult().getSuggest());
        }else{
            return Optional.empty();
        }

    }
    public Boolean checkImageSave(byte[] fileContent,String appId,String openId){
        HttpUtil.HttpRequest.HttpRequestBuilder httpRequestBuilder=HttpUtil.HttpRequest.builder();
        httpRequestBuilder.url("https://api.weixin.qq.com/wxa/img_sec_check");
        httpRequestBuilder.authFunction(httpBearValue);
        httpRequestBuilder.auth(HttpUtil.HttpAuth.builder().user(appId).build());
        httpRequestBuilder.method(HttpUtil.Method.FILE);
        Map<String, HttpUtil.File> postFile=new HashMap<>(4);
        postFile.put("media",HttpUtil.File.builder().content(fileContent).contentType("image/png").fileName("a.png").build());
        httpRequestBuilder.postFile(postFile);

        HttpUtil.HttpResponse sent = HttpUtil.sent(httpRequestBuilder.build());
        log.info(sent.html());
        MediaCheckReturn json = sent.json(MediaCheckReturn.class);
        if(Objects.nonNull(json.getErrcode()) && json.getErrcode()==0){
            return true;
        }
        return false;
    }
    public void checkImageV2(String url,String appId,String openId,String objectId){
        HttpUtil.HttpRequest.HttpRequestBuilder httpRequestBuilder=HttpUtil.HttpRequest.builder();
        httpRequestBuilder.url("https://api.weixin.qq.com/wxa/media_check_async");
        httpRequestBuilder.authFunction(httpBearValue);
        httpRequestBuilder.auth(HttpUtil.HttpAuth.builder().user(appId).build());
        httpRequestBuilder.method(HttpUtil.Method.JSON);
        HashMap<String,String> form=new HashMap<>();
        form.put("version","2");
        form.put("scene","1");
        form.put("media_type","2");
        form.put("media_url",url);
        form.put("openid",openId);
        try {
            log.info("发送到微信服务器json {}",objectMapper.writeValueAsString(form));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        httpRequestBuilder.postObject(form);

        HttpUtil.HttpResponse sent = HttpUtil.sent(httpRequestBuilder.build());
        MediaCheckReturn json = sent.json(MediaCheckReturn.class);
        if(Objects.nonNull(json.getErrcode()) && json.getErrcode()==0){
            StorageEntity storageEntity=new StorageEntity();
            storageEntity.setObjectId(objectId);
            storageEntity.setUrl(url);
            storageEntity.setTraceId(json.getTraceId());
            storageEntity.setCheckResult("N");
            storageRepository.save(storageEntity);
        }
        log.info(sent.html());

    }
    public void httpBearValue2(HttpUtil.HttpAuth httpAuth){
        HttpUtil.HttpAuthReturn apply = httpBearValue.apply(httpAuth);
        log.info(apply.toString());
    }

    /**
     * 微信消息回调
     * 数据格式json
     * @param appId appId
     * @param httpServletRequest  http请求对象
     * @param eventListener   事件处理程序
     * @return 返回微信服务器端接口报文
     */
    public String handleMessage(String appId,HttpServletRequest httpServletRequest,EventListener eventListener) {
        try{
            eventListenerThreadLocal.set(eventListener);
            WxAppInfo wxAppInfo = settingService.getSetting(appId, WxAppInfo.class);
            boolean checkSignature = checkSignature(wxAppInfo,httpServletRequest.getParameter("signature"), httpServletRequest.getParameter("timestamp"), httpServletRequest.getParameter("nonce"), "");
            if (!checkSignature) {
                return "";
            }
            if (httpServletRequest.getParameter("echostr") != null) {
                return httpServletRequest.getParameter("echostr");
            }
            try {
                WxEventMsg wxEventMsg = unWarpCrypt(wxAppInfo, httpServletRequest);
                log.info(wxEventMsg.toString());
                Optional<OutMessage> outMessage = triggerEvent(wxEventMsg.getMessage());
                return warpCrypt(wxEventMsg,wxAppInfo,outMessage);
            } catch (IOException e) {
                log.info("we chat IOException");
            }
            log.info("we chat message");
            return "";
        }finally {
            eventListenerThreadLocal.remove();
        }
    }
    /**
     * 事件分发
     *
     * @param jsonMsg 腾讯服务器发送的与用户交互的信息明文
     * @throws IOException 解析xml出错时报IOException错误
     */
    private <T extends com.microtf.framework.services.miniapp.message.output.OutMessage> Optional<T> triggerEvent(String jsonMsg) throws IOException {
        if (eventListenerThreadLocal.get() == null) {
            return Optional.empty();
        }
        EventListener eventListener = eventListenerThreadLocal.get();
        Message message = objectMapper.readValue(jsonMsg, Message.class);
        log.info("微信消息{}",jsonMsg);
        switch (message.getMsgType()) {
            case "event":
                switch (message.getEvent()) {
                    case "subscribe":
                        return eventListener.onEvent(objectMapper.readValue(jsonMsg, SubscribeEvent.class));
                    case "unsubscribe":
                        return eventListener.onEvent(objectMapper.readValue(jsonMsg, UnsubscribeEvent.class));
                    case "SCAN":
                        return eventListener.onEvent(objectMapper.readValue(jsonMsg, ScanEvent.class));
                    case "LOCATION":
                        return eventListener.onEvent(objectMapper.readValue(jsonMsg, LocationEvent.class));
                    case "CLICK":
                        return eventListener.onEvent(objectMapper.readValue(jsonMsg, ClickEvent.class));
                    case "VIEW":
                        return eventListener.onEvent(objectMapper.readValue(jsonMsg, ViewEvent.class));
                    case "wxa_media_check":
                        return eventListener.onEvent(objectMapper.readValue(jsonMsg, MediaCheckEvent.class));
                    default:
                        break;
                }
                break;
            case "text":
                return eventListener.onMessage(objectMapper.readValue(jsonMsg, TextMessage.class));
            case "image":
                return eventListener.onMessage(objectMapper.readValue(jsonMsg, ImageMessage.class));
            case "voice":
                return eventListener.onMessage(objectMapper.readValue(jsonMsg, VoiceMessage.class));
            case "video":
                return eventListener.onMessage(objectMapper.readValue(jsonMsg, VideoMessage.class));
            case "shortvideo":
                return eventListener.onMessage(objectMapper.readValue(jsonMsg, ShortVideoMessage.class));
            case "location":
                return eventListener.onMessage(objectMapper.readValue(jsonMsg, LocationMessage.class));
            case "link":
                return eventListener.onMessage(objectMapper.readValue(jsonMsg, LinkMessage.class));
            default:
                break;
        }
        return Optional.empty();
    }

    private WxEventMsg unWarpCrypt(WxAppInfo wxAppInfo,HttpServletRequest httpServletRequest) throws IOException {
        String encryptType = httpServletRequest.getParameter("encrypt_type");
        ServletInputStream inputStream = httpServletRequest.getInputStream();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        WxEventMsg wxEventMsg=new WxEventMsg();
        byte[] readByte = new byte[1024];
        int len;
        while ((len = inputStream.read(readByte, 0, readByte.length)) > 0) {
            byteArrayOutputStream.write(readByte, 0, len);
        }
        wxEventMsg.setMessage(byteArrayOutputStream.toString());
        log.info(byteArrayOutputStream.toString());
        if ("aes".equalsIgnoreCase(encryptType)) {
            wxEventMsg.setCrypt(true);
            MessageTransfer messageTransfer = objectMapper.readValue(byteArrayOutputStream.toString(), MessageTransfer.class);
            boolean checkSignature = checkSignature(wxAppInfo,httpServletRequest.getParameter("msg_signature"), httpServletRequest.getParameter("timestamp"), httpServletRequest.getParameter("nonce"), messageTransfer.getEncrypt());
            if (!checkSignature) {
                throw new BizException("Signature error");
            }
            WeChatBizCrypt wechatBizCrypt = new WeChatBizCrypt(wxAppInfo);
            String decrypt = wechatBizCrypt.decrypt(messageTransfer.getEncrypt());
            wxEventMsg.setMessage(decrypt);
            return wxEventMsg;
        } else {
            wxEventMsg.setCrypt(false);
            wxEventMsg.setMessage(byteArrayOutputStream.toString());
            return wxEventMsg;
        }
    }
    private String warpCrypt(WxEventMsg wxEventMsg,WxAppInfo wxAppInfo,Optional<com.microtf.framework.services.miniapp.message.output.OutMessage> message) throws IOException {
        if (!message.isPresent()) {
            log.info("没有消息返回到微信");
            return "";
        }
        if (wxEventMsg.getCrypt()) {
            com.microtf.framework.services.miniapp.message.output.OutMessage message1 = message.get();
            WeChatBizCrypt wechatBizCrypt = new WeChatBizCrypt(wxAppInfo);
            String s = objectMapper.writeValueAsString(message.get());
            String encrypt = wechatBizCrypt.encrypt(wechatBizCrypt.getRandomStr(), s);
            MessageTransfer messageTransfer = new MessageTransfer();
            messageTransfer.setEncrypt(encrypt);
            messageTransfer.setToUserName(message1.getToUserName());
            String timeStamp = Long.toString(System.currentTimeMillis());
            messageTransfer.setNonce(wechatBizCrypt.getRandomStr());
            Optional<String> signature = getSignature(wxAppInfo,timeStamp, messageTransfer.getNonce(), encrypt);
            signature.ifPresent(messageTransfer::setMsgSignature);
            messageTransfer.setTimeStamp(timeStamp);
            return objectMapper.writeValueAsString(messageTransfer);
        } else {
            return objectMapper.writeValueAsString(message.get());
        }
    }
    private boolean checkSignature(WxAppInfo wxAppInfo,String signature, String timestamp, String nonce, String encrypt) {
        Optional<String> signature1 = getSignature(wxAppInfo,timestamp, nonce, encrypt);
        return signature1.filter(signature::equalsIgnoreCase).isPresent();
    }

    private Optional<String> getSignature(WxAppInfo wxAppInfo,String timestamp, String nonce, String encrypt) {
        String[] paramArr = new String[]{wxAppInfo.getEventToken(), timestamp, nonce, encrypt};
        Arrays.sort(paramArr);
        StringBuilder sb = new StringBuilder();
        for (String s : paramArr) {
            sb.append(s);
        }
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
            messageDigest.update(sb.toString().getBytes());
            byte[] digest = messageDigest.digest();
            StringBuilder hexStr = new StringBuilder();
            String shaHex;
            for (byte b : digest) {
                shaHex = Integer.toHexString(b & 0xFF);
                if (shaHex.length() < 2) {
                    hexStr.append(0);
                }
                hexStr.append(shaHex);
            }
            return Optional.of(hexStr.toString().trim());
        } catch (NoSuchAlgorithmException e) {
            log.error("没有微信加解密算法sha1");
            return Optional.empty();
        }
    }
}
